/**
 * tftp.cpp
 * TFTP Managing library.
 * By Group 1
 */
#include "tftp.h"
#include <iostream>
#include <cstring>

using namespace std;

static const char* connectionMode = "octet";

BigEndianShort getBigEndianFromNumber(short number)
{
    unsigned short uNumber = (unsigned short) number;
    BigEndianShort BEShort;
    BEShort.MSB = (uNumber>>8)&0xFF;
    BEShort.LSB = (uNumber>>0)&0xFF;
    return BEShort;
}

short readBigEndian(BigEndianShort BEShort)
{
    unsigned short uNumber = (BEShort.MSB<<8)|BEShort.LSB;
    return (short) uNumber;
}

bool connectToServer(PolonetConn &conn, const char* hostAddress)
{
    /* Open the connection */
    if (!(conn = openConnection((char*)hostAddress, CONNECTION_PORT)))
    {
        cout << "Could not connect to " << hostAddress << " at port:" << CONNECTION_PORT << endl;
        return false;
    }

    /* Wait for the connection establishment.
       If the connection is pending, wait*/
    while (isPending(conn))
        usleep(SLEEP);

    if (!isConnected(conn))
    {
        /* Always close open connections */
        closeConnection(conn);

        cout << "Connection refused from " << hostAddress << " at port:" << CONNECTION_PORT << endl;
        return false;
    }
    else
    {
        // Report successful connection
        return true;
    }

}

void disconnectFromServer(PolonetConn &conn)
{
    closeConnection(conn);
}

int loadBuffer(PolonetConn &conn, char *buffer, int buffersize)
{
    int bytesReceived;

    while (isConnected(conn))
    {
        if ((bytesReceived = receiveData(conn, buffer, buffersize)))
            return bytesReceived;

        /* Wait so the CPU is not clogged */
        usleep(SLEEP);
    }

    /* Just in case there is data left */
    if ((bytesReceived = receiveData(conn, buffer, buffersize)))
        return bytesReceived;

    return 0;
}


void transmitECHOPacket(PolonetConn &conn, const char* message)
{
    int messageLength = strlen(message);
    int packetLength = 2 + messageLength + 1;
    BigEndianShort opcode = getBigEndianFromNumber(ECHO);

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;

    // Copio el mensaje al paquete.
    for(int i=0; i < messageLength; i++)
        packet[i+2] = message[i];
    // Agrego el terminador
    packet[messageLength+2] = '\0';

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

void transmitRRQPacket(PolonetConn &conn, const char* filename)
{
    int filenameLength = strlen(filename);
    int modeLength = strlen(connectionMode);
    int packetLength = 2 + filenameLength + 1 + modeLength + 1;
    BigEndianShort opcode = getBigEndianFromNumber(RRQ);

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;

    // Copio el nombre al paquete.
    for(int i=0; i < filenameLength; i++)
        packet[i+2] = filename[i];
    // Agrego el terminador
    packet[filenameLength+2] = '\0';

    // Copio el modo al paquete.
    for(int i=0; i < modeLength; i++)
        packet[i+filenameLength+3] = connectionMode[i];
    // Agrego el terminador
    packet[filenameLength+modeLength+3] = '\0';

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

void transmitWRQPacket(PolonetConn &conn, const char* filename)
{
    int filenameLength = strlen(filename);
    int modeLength = strlen(connectionMode);
    int packetLength = 2 + filenameLength + 1 + modeLength + 1;
    BigEndianShort opcode = getBigEndianFromNumber(WRQ);

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;

    // Copio el nombre al paquete.
    for(int i=0; i < filenameLength; i++)
        packet[i+2] = filename[i];
    // Agrego el terminador
    packet[filenameLength+2] = '\0';

    // Copio el modo al paquete.
    for(int i=0; i < modeLength; i++)
        packet[i+filenameLength+3] = connectionMode[i];
    // Agrego el terminador
    packet[filenameLength+modeLength+3] = '\0';

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

void transmitACKPacket(PolonetConn &conn, short blockNumber)
{
    BigEndianShort opcode = getBigEndianFromNumber(ACK);
    BigEndianShort BEblockNumber = getBigEndianFromNumber(blockNumber);
    int packetLength = 4;

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;
    packet[2] = BEblockNumber.MSB;
    packet[3] = BEblockNumber.LSB;

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

void transmitDIEPacket(PolonetConn &conn)
{
    BigEndianShort opcode = getBigEndianFromNumber(DIE);
    int packetLength = 2;

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

void transmitDATAPacket(PolonetConn &conn, short blockNumber, char* data, const int dataSize)
{
    int packetLength = 2 + 2 + dataSize;
    BigEndianShort opcode = getBigEndianFromNumber(DATA);
    BigEndianShort BEblockNumber = getBigEndianFromNumber(blockNumber);

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;
    packet[2] = BEblockNumber.MSB;
    packet[3] = BEblockNumber.LSB;

    // Copio el mensaje al paquete.
    for(int i=0; i < dataSize; i++)
        packet[i+4] = data[i];

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

void transmitERRORPacket(PolonetConn &conn, short errorNumber, const char* errmsg)
{
    int errmsgLength = strlen(errmsg);
    int packetLength = 2 + 2 + errmsgLength + 1;
    BigEndianShort opcode = getBigEndianFromNumber(ERROR);
    BigEndianShort BEerrorNumber = getBigEndianFromNumber(errorNumber);

    char* packet = new char[packetLength];

    packet[0] = opcode.MSB;
    packet[1] = opcode.LSB;
    packet[2] = BEerrorNumber.MSB;
    packet[3] = BEerrorNumber.LSB;

    // Copio el mensaje al paquete.
    for(int i=0; i < errmsgLength; i++)
        packet[i+4] = errmsg[i];
    // Agrego el terminador
    packet[errmsgLength+4] = '\0';

    /* Send the packet */
    sendData(conn, packet, packetLength);
    delete[] packet;
}

short getPacketType(char* packet)
{
    BigEndianShort BEopcode;
    BEopcode.MSB = packet[0];
    BEopcode.LSB = packet[1];
    return readBigEndian(BEopcode);
}

bool readRRQWRQPacket(char* &filename, char* &mode, char* packet)
{
    BigEndianShort BEopcode;
    BEopcode.MSB = packet[0];
    BEopcode.LSB = packet[1];
    short opcode = readBigEndian(BEopcode);

    if (opcode == RRQ || opcode == WRQ)
    {
        filename = &(packet[2]);
        mode = &(packet[2+1+strlen(filename)]);
        return true;
    }
    else
        return false;
}

bool readECHOPacket(char* &message, char* packet)
{
    BigEndianShort BEopcode;
    BEopcode.MSB = packet[0];
    BEopcode.LSB = packet[1];
    short opcode = readBigEndian(BEopcode);

    if (opcode == ECHO)
    {
        message = &(packet[2]);
        return true;
    }
    else
        return false;
}

bool readDATAPacket(char* data, short &blockNumber, char* packet,const int packetsize)
{
    // Nota: Esta funcion ESCRIBE AL PUNTERO DATA!
    BigEndianShort BEopcode;
    BEopcode.MSB = packet[0];
    BEopcode.LSB = packet[1];
    short opcode = readBigEndian(BEopcode);

    if (opcode == DATA)
    {
        BigEndianShort BEblockNumber;
        BEblockNumber.MSB = packet[2];
        BEblockNumber.LSB = packet[3];
        blockNumber = readBigEndian(BEblockNumber);

        for (int i = 0; i<(packetsize-4); i++)
            data[i] = packet[4+i];

        return true;
    }
    else
        return false;
}

bool readERRORPacket(char* &errmsg,short &errorNumber, char* packet)
{
    BigEndianShort BEopcode;
    BEopcode.MSB = packet[0];
    BEopcode.LSB = packet[1];
    short opcode = readBigEndian(BEopcode);

    if (opcode == ERROR)
    {
        BigEndianShort BEerrorNumber;
        BEerrorNumber.MSB = packet[2];
        BEerrorNumber.LSB = packet[3];
        errorNumber = readBigEndian(BEerrorNumber);

        errmsg = &(packet[4]);

        return true;
    }
    else
        return false;
}

bool readACKPacket(short &blockNumber, char* packet)
{
    BigEndianShort BEopcode;
    BEopcode.MSB = packet[0];
    BEopcode.LSB = packet[1];
    short opcode = readBigEndian(BEopcode);

    if (opcode == ACK)
    {
        BigEndianShort BEblockNumber;
        BEblockNumber.MSB = packet[2];
        BEblockNumber.LSB = packet[3];
        blockNumber = readBigEndian(BEblockNumber);
        return true;
    }
    else
        return false;
}
